package com.juqimiao.outposts.spring.oauth2.mobile.provider;


import com.juqimiao.outposts.spring.oauth2.mobile.SecurityCodeGenerator;
import com.juqimiao.outposts.spring.oauth2.mobile.token.UserMobileCodeAuthenticationToken;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.userdetails.*;
import org.springframework.security.core.userdetails.cache.NullUserCache;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.lang.String;
/**
 * 支持移动验证码授权的类.
 * @author collin
 * @date 2018/4/3
 */
@Service
public class SecurityCodeAuthenticationProvder implements AuthenticationProvider {

    private static final String CODE_ONLY_SUPPORTS = "AbstractUserDetailsAuthenticationProvider.onlySupports";
    private static final String DEFAULT_MESSAGE = "Only UsernamePasswordAuthenticationToken is supported";
    private static final String NONE_PROVIDED = "NONE_PROVIDED";
    private static final String CODE_BAD_CREDENTIALS = "AbstractUserDetailsAuthenticationProvider.badCredentials";
    private static final String BAD_CREDENTIALS = "Bad credentials";
    private static final String USER_NULL = "retrieveUser returned null - a violation of the interface contract";
    private static final String AUTHENTICATION_FAILED = "Authentication failed: no credentials provided";
    private static final String NOT_MATCHED_MESSAGE = "Authentication failed: mobile code does not matched.";
    private static final String CODE_GENERATOR_NULL = "code Generator cannot be null";
    private static final String USER_ACCOUNT_LOCKED = "User account is locked";
    private static final String CODE_LOCKED = "AbstractUserDetailsAuthenticationProvider.locked";
    private static final String USER_ACCOUNT_DISABLED = "User account is disabled";
    private static final String CODE_DISABLED = "AbstractUserDetailsAuthenticationProvider.disabled";
    private static final String USER_ACCOUNT_EXPIRED = "User account is expired";
    private static final String CODE_EXPIRED = "AbstractUserDetailsAuthenticationProvider.expired";
    private static final String USER_ACCOUNT_CREDENTIALS_EXPIRED = "User account credentials have expired";
    private static final String CODE_CREDENTIALS_EXPIRED = "AbstractUserDetailsAuthenticationProvider.credentialsExpired";
    protected final Log logger = LogFactory.getLog(getClass());

    /**
     * 验证码生成器.
     */
    private SecurityCodeGenerator codeGenerator;
    /**
     * 当比对验证码的时候需要一个 {@link SecurityCodeGenerator} 的实现。
     */
    private volatile String userNotFoundCodeGenerator;
    /**
     * 获取用户信息的服务. {@link UserDetailsService} 的一个实现类。
     */
    private UserDetailsService userDetailsService;


    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    private UserCache userCache = new NullUserCache();
    private boolean forcePrincipalAsString = false;
    protected boolean hideUserNotFoundExceptions = true;
    private UserDetailsChecker preAuthenticationChecks = new SecurityCodeAuthenticationProvder.DefaultPreAuthenticationChecks();
    private UserDetailsChecker postAuthenticationChecks = new SecurityCodeAuthenticationProvder.DefaultPostAuthenticationChecks();
    private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();

    /**
     * 默认构造函数
     * 未来需要在其中初始化CodeGenerator对象。
     */
    public SecurityCodeAuthenticationProvder() {

    }

    /**
     * 用户授权过程.
     *
     * @param authentication 仅限于 {@link UserMobileCodeAuthenticationToken}.
     * @return 返回一个有效的 {@link Authentication}.
     * @throws AuthenticationException {@link AuthenticationException}
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UserMobileCodeAuthenticationToken.class, authentication,
                () -> messages.getMessage(CODE_ONLY_SUPPORTS, DEFAULT_MESSAGE));

        // Determine username
        String username = (authentication.getPrincipal() == null) ? NONE_PROVIDED : authentication.getName();

        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);

        if (user == null) {
            cacheWasUsed = false;

            try {
                user = retrieveUser(username,
                        (UserMobileCodeAuthenticationToken) authentication);
            } catch (UsernameNotFoundException notFound) {
                logger.debug("User '" + username + "' not found");

                if (hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(messages.getMessage(
                            CODE_BAD_CREDENTIALS,
                            BAD_CREDENTIALS));
                } else {
                    throw notFound;
                }
            }

            Assert.notNull(user, USER_NULL);
        }

        try {
            preAuthenticationChecks.check(user);
            additionalAuthenticationChecks(user,
                    (UserMobileCodeAuthenticationToken) authentication);
        } catch (AuthenticationException exception) {
            if (cacheWasUsed) {
                // There was a problem, so try again after checking
                // we're using latest data (i.e. not from the cache)
                cacheWasUsed = false;
                user = retrieveUser(username,
                        (UserMobileCodeAuthenticationToken) authentication);
                preAuthenticationChecks.check(user);
                additionalAuthenticationChecks(user,
                        (UserMobileCodeAuthenticationToken) authentication);
            } else {
                throw exception;
            }
        }

        postAuthenticationChecks.check(user);

        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;

        if (forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }

        return createSuccessAuthentication(principalToReturn, authentication, user);
    }

    /**
     * ProviderManager {@link ProviderManager }
     * 通过此方法判断 UserMobileCodeAuthenticationToken {@link UserMobileCodeAuthenticationToken}
     * 对应的 AuthenticationProvider {@link SecurityCodeAuthenticationProvder}
     *
     * @param authentication {@link SecurityCodeAuthenticationProvder}.
     * @return 对应AuthenticationToken是否被支持.
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return (UserMobileCodeAuthenticationToken.class
                .isAssignableFrom(authentication));
    }



    /**
     * 负责验证传入的验证码信息是否正确.
     *
     * @param userDetails    用户信息.
     * @param authentication {@link UserMobileCodeAuthenticationToken}
     * @throws AuthenticationException {@link AuthenticationException}
     */
    protected void additionalAuthenticationChecks(UserDetails userDetails,
                                                  UserMobileCodeAuthenticationToken authentication)
            throws AuthenticationException {

        if (authentication.getCredentials() == null) {
            logger.debug(AUTHENTICATION_FAILED);

            throw new BadCredentialsException(messages.getMessage(CODE_BAD_CREDENTIALS, BAD_CREDENTIALS));
        }

        String mobileCode = authentication.getCredentials().toString();
        String hardCode = "123456";
        if (!mobileCode.equals(hardCode)) {
            logger.debug(NOT_MATCHED_MESSAGE);

            throw new BadCredentialsException(messages.getMessage(CODE_BAD_CREDENTIALS, BAD_CREDENTIALS));
        }
    }

    protected void doAfterPropertiesSet() throws Exception {
        Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
    }

    /**
     * 获取用户信息.
     *
     * @param username       用户标识.
     * @param authentication {@link UserMobileCodeAuthenticationToken}
     * @return 返回 {@link UserDetails}实例.
     * @throws AuthenticationException {@link AuthenticationException}
     */
    protected final UserDetails retrieveUser(String username, UserMobileCodeAuthenticationToken authentication)
            throws AuthenticationException {
        prepareTimingAttackProtection();
        try {
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        } catch (UsernameNotFoundException ex) {
            mitigateAgainstTimingAttack(authentication);
            throw ex;
        } catch (InternalAuthenticationServiceException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }

    /**
     * 创建一个 successful {@link Authentication} 对象.
     * <p>
     * 可被复写.
     * </p>
     *
     * @param principal      principal 对象. 通常保存一个用户的标识信息的字符串.
     * @param authentication {@link Authentication}
     * @param user           一个被加载的用户信息.
     * @return 返回成功的authentication token
     */
    protected Authentication createSuccessAuthentication(Object principal,
                                                         Authentication authentication, UserDetails user) {
        // Ensure we return the original credentials the user supplied,
        // so subsequent attempts are successful even with encoded passwords.
        // Also ensure we return the original getDetails(), so that future
        // authentication events after cache expiry contain the details
        UserMobileCodeAuthenticationToken result = new UserMobileCodeAuthenticationToken(
                principal, authentication.getCredentials(),
                authoritiesMapper.mapAuthorities(user.getAuthorities()));
        result.setDetails(authentication.getDetails());
        return result;

    }

    /**
     * 在Authenticate过程前执行.
     */
    private void prepareTimingAttackProtection() {
        if (this.userNotFoundCodeGenerator == null) {
            this.userNotFoundCodeGenerator = this.codeGenerator.getNullCode();
        }
    }

    /**
     * mitigateAgainstTimingAttack.
     *
     * @param authentication {@link UserMobileCodeAuthenticationToken}
     */
    private void mitigateAgainstTimingAttack(UserMobileCodeAuthenticationToken authentication) {
        if (authentication.getCredentials() != null) {
            String presentedMobileCode = authentication.getCredentials().toString();
            this.codeGenerator.validate(presentedMobileCode);
        }
    }

    /**
     * Sets the CodeGenerator instance to be used to generate a code and validate code. If
     *
     * @param codeGenerator must be an instance of one of the {@code PasswordEncoder}
     *                      types.
     */
    public void setCodeGenerator(SecurityCodeGenerator codeGenerator) {
        Assert.notNull(codeGenerator, CODE_GENERATOR_NULL);
        this.codeGenerator = codeGenerator;
        this.userNotFoundCodeGenerator = null;
    }

    /**
     * 获得一个验证码生成器对象.
     *
     * @return 验证码生成器对象.
     */
    protected SecurityCodeGenerator getCodeGenerator() {
        return codeGenerator;
    }

    /**
     * 设置获取用户信息的服务类 {@link UserDetailsService}.
     *
     * @param userDetailsService {@link UserDetailsService}.
     */
    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    /**
     * 获取用户信息服务类 {@link UserDetailsService}.
     *
     * @return {@link UserDetailsService}对象.
     */
    protected UserDetailsService getUserDetailsService() {
        return userDetailsService;
    }

    /**
     * 默认的用户状态检查.
     *
     * @author collin
     * @date 2018/4/3
     */
    private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
        @Override
        public void check(UserDetails user) {
            if (!user.isAccountNonLocked()) {
                logger.debug(USER_ACCOUNT_LOCKED);

                throw new LockedException(messages.getMessage(CODE_LOCKED, USER_ACCOUNT_LOCKED));
            }

            if (!user.isEnabled()) {
                logger.debug(USER_ACCOUNT_DISABLED);

                throw new DisabledException(messages.getMessage(CODE_DISABLED, USER_ACCOUNT_DISABLED));
            }

            if (!user.isAccountNonExpired()) {
                logger.debug(USER_ACCOUNT_EXPIRED);

                throw new AccountExpiredException(messages.getMessage(CODE_EXPIRED, USER_ACCOUNT_EXPIRED));
            }
        }
    }

    /**
     * 默认的用户账户到期检查.
     *
     * @author collin
     * @date 2018/4/3
     */
    private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
        @Override
        public void check(UserDetails user) {
            if (!user.isCredentialsNonExpired()) {
                logger.debug(USER_ACCOUNT_CREDENTIALS_EXPIRED);

                throw new CredentialsExpiredException(messages.getMessage(CODE_CREDENTIALS_EXPIRED,
                        USER_ACCOUNT_CREDENTIALS_EXPIRED));
            }
        }
    }
}
